﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using AngleSharp.Common;
using AngleSharp.Html.Parser;
using ClassBuilder;
using ClassBuilder.Comment;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace ConsoleApp
{
    public class Download
    {
        public const string OriginUrl = "https://echarts-www.cdn.bcebos.com/zh/documents/option-parts/";

        public static string Default_NameSpace { get; set; } = "WUDI.ECharts";
        public static string ClassPath = Path.GetFullPath("../../../../WUDI.ECharts");
        public static string EnumsDir = Path.GetFullPath("../../../../WUDI.ECharts/Enums");
        public static Dictionary<string, CSEnum> dict_Enum = new Dictionary<string, CSEnum>(StringComparer.OrdinalIgnoreCase);

        public static Dictionary<string, string[]> dict_M_Enum = new Dictionary<string, string[]>();
        public static Dictionary<string, HashSet<KeyValuePair<string, string>>> dict_M_Name = new Dictionary<string, HashSet<KeyValuePair<string, string>>>();
        
        public static WebClient client = new WebClient()
        {
            Encoding = Encoding.UTF8
        };

        #region MyRegion
        //[Url("option-outline.js")]
        //[RegexReplace("window.__EC_DOC_option_outline =", "")]
        //public JObject Json_Option_Outline { get; private set; }

        //[Url("option.js")]
        //[RegexReplace("window.__EC_DOC_option =", "")]
        //public JObject Json_Option { get; private set; }

        //public void Init()
        //{
        //    var type = this.GetType();

        //    var props = type.GetProperties();

        //    foreach (PropertyInfo prop in props)
        //    {
        //        var address = prop.GetCustomAttribute<UrlAttribute>()?.Address;
        //        var content = client.DownloadString(OriginUrl + address);

        //        var rgx = prop.GetCustomAttribute<RegexReplaceAttribute>();

        //        var newContent = Regex.Replace(content, rgx.Pattern, rgx.Replacement, RegexOptions.Compiled | RegexOptions.Multiline);

        //        var jObj = JObject.Parse(newContent);

        //        prop.SetValue(this, jObj);

        //        File.WriteAllText(Path.GetFullPath("../../../json/" + prop.Name + ".json"), jObj.ToString(Formatting.Indented));
        //    }
        //} 
        #endregion

        public JObject Json_Option_Outline { get; private set; }

        //public JObject Json_Option { get; private set; }

        /// <summary>
        /// 下载所有json文件
        /// </summary>
        public void Init()
        {
            Json_Option_Outline = GetObject(null, "outline").DescJobj;
            //Json_Option = GetObject(null, null);
            if (Json_Option_Outline != null)
            {
                var children = Json_Option_Outline["children"].Children();
                foreach (JObject jObj in children)
                {
                    var isObject = jObj.GetOrDefault("isObject", false).Value<bool>();
                    var isArray = jObj.GetOrDefault("isArray", false).Value<bool>();
                    var prop = jObj.GetOrDefault("prop", null).Value<string>();

                    if (isObject)
                    {
                        if (prop.NotEmpty())
                        {
                            GetObject(prop);
                        }
                        else
                        {

                        }
                    }
                    else if (isArray)
                    {
                        var jArr = jObj["children"] as JArray;
                        foreach (JObject itemObj in jArr)
                        {
                            var arrayItemType = itemObj.GetOrDefault("arrayItemType", null).Value<string>();
                            if (arrayItemType.NotEmpty())
                            {
                                GetObject(prop, arrayItemType);
                            }
                            else
                            {

                            }
                        }
                    }
                    else
                    {

                    }
                }
            }

            if (!Directory.Exists(EnumsDir))
            {
                Directory.CreateDirectory(EnumsDir);
            }
        }

        /// <summary>
        /// 清理删除
        /// </summary>
        public void Clear()
        {
            foreach (var path in Directory.GetFiles(ClassPath, "*.cs", SearchOption.AllDirectories))
            {
                File.Delete(path);
            }
        }


        public (JObject DescJobj, string filePath) GetObject(string prop = null, string itemType = null)
        {
            var name = string.Empty;

            if (prop.IsEmpty())
            {
                name = "option";
            }
            else
            {
                name = $"option.{prop.ToFirstLowerStr()}";
            }

            if (itemType.NotEmpty())
            {
                name += "-" + itemType;
            }

            var file_path = Path.GetFullPath($"../../../json/{name}.json");
            var js_name = $"{name}.js?" + new Random().NextDouble();
            var content = string.Empty;
            JObject Jobj = null;
            if (File.Exists(file_path))
            {
                content = File.ReadAllText(file_path);
                Jobj = JObject.Parse(content);
            }
            else
            {
                content = client.DownloadString(OriginUrl + js_name);
                var newContent = Regex.Replace(content, $"window.__EC_DOC_option.*? =", "", RegexOptions.Compiled | RegexOptions.Multiline);
                Jobj = JObject.Parse(newContent);
                newContent = Jobj.ToString(Formatting.Indented);
                File.WriteAllText(file_path, newContent);
            }
            return (Jobj, file_path);
        }


        public void BuildEnums()
        {
            var dir = Path.GetFullPath("../../../json/");
            var files = Directory.GetFiles(dir, "option.*.json");
            var enum_path = Path.GetFullPath("../../../enum.log");
            if (File.Exists(enum_path))
            {
                File.Delete(enum_path);
            }

            foreach (var file in files)
            {
                var jObj = JObject.Parse(File.ReadAllText(file));
                var items = jObj.Children();
                foreach (JProperty item in items)
                {
                    var name = item.Name;
                    var itemJobj = item.Value as JObject;

                    if (CheckEnum(file, name, itemJobj, out CSEnum csEnum))
                    {
                        Console.WriteLine(csEnum.EnumName);
                    }
                }
            }

            var m_enum_json = JsonConvert.SerializeObject(dict_M_Enum, formatting: Formatting.Indented);
            var m_enum_name_json = JsonConvert.SerializeObject(dict_M_Name, formatting: Formatting.Indented);
            var enumTmp = Path.GetFullPath("../../../enum/");
            if (!Directory.Exists(enumTmp))
            {
                Directory.CreateDirectory(enumTmp);                    
            }
            File.WriteAllText(Path.Combine(enumTmp, "m_enum.json"), m_enum_json);
            File.WriteAllText(Path.Combine(enumTmp, "m_enum_name.json"), m_enum_name_json);

            NameHandle();

            foreach (var item_Enum in dict_Enum)
            {
                File.WriteAllText(Path.Combine(EnumsDir, item_Enum.Value.EnumName + ".cs"), item_Enum.Value.Value);
            }
        }

        public void NameHandle()
        {
            var excludeNames = new List<string>()
            {
                "option","series"
            };
            var enum_tj_path = Path.Combine(Path.GetFullPath("../../../enum/"), "enum_segment.txt");
            if (File.Exists(enum_tj_path))
            {
                File.Delete(enum_tj_path);
            }
            foreach (var kv in dict_M_Name)
            {
                var vals = kv.Value;
                var lst = new List<string>();
                foreach (var item in vals)
                {
                    var arr1 = item.Key.Split(new string[] { ".","-" }, StringSplitOptions.RemoveEmptyEntries);
                    var arr2 = item.Value.Split(new string[] { ".", "-" }, StringSplitOptions.RemoveEmptyEntries);

                    foreach (var a1 in arr1)
                    {
                        lst.AddRange(a1.Segment());
                    }
                    foreach (var a2 in arr2)
                    {
                        lst.AddRange(a2.Segment());
                    }
                }
                var members = dict_M_Enum[kv.Key];
                File.AppendAllText(enum_tj_path, string.Join("/", members) + "\r\n\r\n");
                lst = lst.Where(t => !excludeNames.Contains(t)).ToList();
                var groups = lst.GroupBy(t => t).OrderByDescending(t => t.Count()).ToList().Take(10);
                foreach (var gp in groups)
                {
                    Console.WriteLine(gp.Key + "\t" + gp.Count());
                    File.AppendAllText(enum_tj_path, gp.Key + "\t" + gp.Count() + "\r\n");
                }
               
                Console.WriteLine("============================================");
                File.AppendAllText(enum_tj_path, "============================================\r\n");
            }
        }

        /// <summary>
        /// 检查节点是否可作为枚举类型
        /// </summary>
        /// <param name="file"></param>
        /// <param name="name"></param>
        /// <param name="itemDescJObj"></param>
        /// <param name="outEnum"></param>
        /// <returns></returns>
        public bool CheckEnum(string file, string name, JObject itemDescJObj, out CSEnum outEnum)
        {
            outEnum = null;
            bool isEnum = false;

            //elements-group.$action
            if (itemDescJObj == null || name.Contains("$"))
            {
                return false;
            }
            var fileName = Path.GetFileNameWithoutExtension(file);
            if (itemDescJObj.TryGetValue("uiControl", out JToken propToken)
                && propToken is JObject props
                && props.TryGetValue("type", out JToken typeToken)
                && typeToken is JValue typeVal
                && typeVal.Value?.ToString() == "enum"
                )
            {
                if (props.TryGetValue("default", out JToken defaultToken)
                    &&
                props.TryGetValue("options", out JToken enumToken)
                    )
                {
                    var defaultValue = (defaultToken as JValue).Value<string>();
                    var members = (enumToken as JValue).Value<string>().Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);

                    //排除不是连续字母的枚举
                    if (members.All(s =>
                        Regex.IsMatch(s, @"^[a-z]+$", RegexOptions.Compiled | RegexOptions.IgnoreCase)))
                    {
                        var str = $"{name}\t\t\t{defaultValue}\t{string.Join(",", members)}";
                        var desc_html = (itemDescJObj.GetOrDefault("desc", string.Empty) as JValue)?.Value<string>();
                        var parser = new HtmlParser();
                        var doc = parser.ParseDocument(desc_html);
                        var cmt = new CSComment(doc.QuerySelector("p").TextContent);

                        if (members.Length <= 1)
                        {
                            return false;
                        }

                        var mid = members.MId();

                        dict_M_Enum[mid] = members;

                        dict_M_Name.AddHashSet(mid, new KeyValuePair<string, string>(name, fileName));

                        var enumName = GetEnumName(name, fileName);
                        CSEnum csEnum = new CSEnum(enumName, Default_NameSpace, cmt);
                        csEnum.AddMembers(members);
                        if (dict_Enum.TryGetValue(csEnum.EnumName, out CSEnum tmpEnum))
                        {
                            if (csEnum.Id == tmpEnum.Id)
                            {
                                outEnum = tmpEnum;
                                isEnum = true;
                            }
                            else
                            {
                                //增加新 枚举值
                                //tmpEnum.AddMembers(csEnum.Members.ToArray());
                                //dict_Enum[csEnum.EnumName] = tmpEnum;

                                var diff = csEnum.Members.Except(tmpEnum.Members).ToList();
                                if (!diff.Any())
                                {
                                    // 枚举值 已经包含,但意思可能不一样
                                    tmpEnum.Comment = null;
                                    dict_Enum[csEnum.EnumName] = tmpEnum;

                                    outEnum = tmpEnum;
                                    isEnum = true;
                                }
                                else
                                {
                                    // todo:枚举名称一致，枚举值冲突，如果要解决冲突，需要优化 GetEnumName 枚举名称生成规则。
                                }
                            }
                        }
                        else
                        {
                            dict_Enum.Add(csEnum.EnumName, csEnum);

                            outEnum = csEnum;
                            isEnum = true;
                        }
                    }
                }
                else
                {

                }

            }
            else if (itemDescJObj.TryGetValue("desc", out JToken descToken))
            {
                var desc_html = (descToken as JValue)?.Value<string>();
                if (desc_html.Contains("可选") || desc_html.Contains("可取值：") || desc_html.Contains("可取值为："))
                {
                    var parser = new HtmlParser();
                    var doc = parser.ParseDocument(desc_html);
                    var nodes = doc.QuerySelector("ul")?.QuerySelectorAll("li");
                    if (nodes != null && nodes.Length > 0)
                    {
                        var cmt = new CSComment(doc.QuerySelector("p").TextContent);
                        var dictMC = new Dictionary<string, string>();
                        foreach (var node in nodes)
                        {
                            var strEnum = string.Empty;
                            var strDesc = string.Empty;
                            if (node.InnerHtml.Contains("："))
                            {
                                //https://echarts.apache.org/zh/option.html#series-bar.seriesLayoutBy
                                var strs = node.InnerHtml.NoHtml().Split(new string[] { "：" }, StringSplitOptions.RemoveEmptyEntries);
                                if (strs.Length == 2)
                                {
                                    //https://echarts.apache.org/zh/option.html#dataZoom-inside.rangeMode
                                    strEnum = strs[0].Replace("模式", "").Trim();
                                    strDesc = strs[1];
                                }
                                else
                                {
                                    continue;
                                }
                            }
                            else
                            {
                                //https://echarts.apache.org/zh/option.html#angleAxis.type
                                strEnum = node.QuerySelector("code")?.InnerHtml;
                                if (strEnum.IsEmpty())
                                {
                                    continue;
                                }
                                strDesc = Regex.Replace(node.TextContent.Replace(strEnum, ""), @"\s", "");
                                strEnum = strEnum.Replace("'", "");
                            }

                            if (strEnum.NotEmpty())
                            {
                                dictMC.Add(strEnum, strDesc);
                            }
                        }

                        var members = dictMC.Keys.ToArray();

                        if (members.Length <= 1 || members.Any(t => t.Contains("/")))
                        {
                            return false;
                        }


                        var mid = members.MId();

                        dict_M_Enum[mid] = members;

                        dict_M_Name.AddHashSet(mid, new KeyValuePair<string, string>(name, fileName));

                        var enumName = GetEnumName(name, fileName);
                        CSEnum csEnum = new CSEnum(enumName, Default_NameSpace, cmt);
                        foreach (var item in dictMC)
                        {
                            csEnum.AddMember(item.Key, item.Value);
                        }
                        if (dict_Enum.TryGetValue(csEnum.EnumName, out CSEnum tmpEnum))
                        {
                            if (csEnum.Id == tmpEnum.Id)
                            {
                                outEnum = tmpEnum;
                                isEnum = true;
                            }
                            else
                            {
                                //增加新 枚举值
                                //tmpEnum.AddMembers(csEnum.Members.ToArray());
                                //dict_Enum[csEnum.EnumName] = tmpEnum;

                                var diff = csEnum.Members.Except(tmpEnum.Members).ToList();
                                if (!diff.Any())
                                {
                                    // 枚举值 已经包含,但意思可能不一样
                                    tmpEnum.Comment = null;
                                    dict_Enum[csEnum.EnumName] = tmpEnum;

                                    outEnum = tmpEnum;
                                    isEnum = true;
                                }
                                else
                                {
                                    // todo:枚举名称一致，枚举值冲突，如果要解决冲突，需要优化 GetEnumName 枚举名称生成规则。
                                }
                            }
                        }
                        else
                        {
                            dict_Enum.Add(csEnum.EnumName, csEnum);

                            outEnum = csEnum;
                            isEnum = true;
                        }
                    }
                }
                else
                {

                }
            }
            else
            {

            }
            return isEnum;
        }


        public static string GetEnumName(string nodeName, string fileName)
        {
            var resName = string.Empty;
            nodeName = nodeName.Replace("<style_name>.", "");
            var strs = nodeName.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries);
            var sLast = strs.Last();

            if (sLast == "type" || sLast == "brushType" || sLast == "position" || sLast == "layout")
            {
                if (strs.Length > 1 && !fileName.Contains(".series-"))
                {
                    var arr = strs.TakeLast(2).Select(t => t.ToFirstUpperStr());
                    resName = string.Join("", arr);
                }
                else
                {
                    var arr = fileName.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries);
                    resName = arr.Last().Split('-').Last().ToFirstUpperStr() + resName.ToFirstUpperStr();
                }

                //if (dict_Enum.ContainsKey(resName))
                //{

                //}

                //if (dict_Enum.TryGetValue(resName, out CSEnum outEnum))
                //{

                //}
            }
            else
            {
                resName = sLast.ToFirstUpperStr();
            }

            if (resName.StartsWith("$"))
            {

            }
            return resName;
        }


        public void BuildClass(JToken jToken, (JObject DescJobj, string filePath) descInfo, ref CSClass currCls)
        {
            if (jToken != null)
            {
                var children = jToken["children"]?.Children().ToList();

                if (children != null && children.Any())
                {
                    foreach (JToken prop in children)
                    {
                        SetProp(prop, descInfo, currCls);
                    }
                    currCls.ClassWriteFile();
                }
                else
                {
                    currCls.ClassWriteFile();
                }
            }
        }

        public void SetProp(JToken jProp, (JObject DescJobj, string filePath) descInfo, CSClass cls)
        {
            JObject descJObj = descInfo.DescJobj;
            var filePath = descInfo.filePath;

            var lstType = new List<string>();

            var curNode = jProp.First;

            if (jProp is JObject jObj)
            {
                var currTypeNode = jObj["type"];
                var propName = jObj.GetOrDefault("prop", null)?.Value<string>();
                var defaultValue = jObj.GetOrDefault("default", null)?.Value<string>().Trim('\'');
                var isObject = jObj.GetOrDefault("isObject", false).Value<bool>();
                var isArray = jObj.GetOrDefault("isArray", false).Value<bool>();

                CSComment csCmt = null;

                var descNodeName = propName;

                var propDescJObj = (descJObj[propName] as JObject);

                if (cls.NameSpace.Count(t => t == '.') > 2)
                {
                    descNodeName = cls.JsonNodeName(propName);

                    propDescJObj = (descJObj[descNodeName] as JObject);

                    if (currTypeNode == null && propDescJObj != null) //option-outline.json 中没有指定 type 时
                    {
                        currTypeNode = (propDescJObj.GetOrDefault("uiControl", null) as JObject)?.GetOrDefault("type", null);
                    }
                }

                var desc = propDescJObj?.GetOrDefault("desc", null)?.Value<string>()?.NoHtml().CheckDesc();

                if (desc.NotEmpty())
                {
                    csCmt = new CSComment(desc);
                }

                var lstProp = new List<string>()
                {
                    "minorSplitLine",
                };

                if (lstProp.Contains(propName))
                {

                }

                var ckEnum = CheckEnum(descInfo.filePath, propName, propDescJObj, out CSEnum outEnum);

                if (ckEnum)
                {
                    if (outEnum.FullName.Contains("WUDI.ECharts.AxisPointerType"))
                    {

                    }

                    cls.AddProperty(outEnum.FullName, propName, defaultValue, csCmt, true);
                }
                else if (isObject)
                {
                    if (propName.Equals("rich", StringComparison.OrdinalIgnoreCase))
                    {
                        AddProp(cls, "dynamic", propName, defaultValue, csCmt);
                    }
                    else
                    {
                        var nameSpace = cls.NameSpace + "." + propName.ToFirstUpperStr();
                        CSClass newCls = new CSClass(propName.ToFirstUpperStr(), nameSpace, csCmt);
                        newCls.Tag = cls;
                        var newDescInfo = descInfo;

                        //第2级才有 json描述文件
                        if (nameSpace.Count(t => t == '.') == 2)
                        {
                            newDescInfo = GetObject(propName);
                        }
                        BuildClass(jObj, newDescInfo, ref newCls);

                        // show 对象 默认值是false ，文档有点问题？
                        //todo:https://echarts.apache.org/zh/option.html#angleAxis.minorSplitLine.show
                        if (defaultValue.NotNull())
                        {
                            defaultValue = null;
                        }

                        AddProp(cls, newCls.FullName, propName, defaultValue, csCmt);
                    }
                }
                else if (isArray)
                {

                }
                else
                {
                    var propClassName = "dynamic";

                    if (currTypeNode.NotNull())
                    {
                        if (currTypeNode is JValue jVal)
                        {
                            var type = jVal.Value.ToString();
                            if (type == "number")
                            {
                                if (defaultValue.NotEmpty())
                                {
                                    if (int.TryParse(default, out int intVal))
                                    {
                                        propClassName = "int";
                                    }
                                    else if (double.TryParse(default, out double dleVal))
                                    {
                                        propClassName = "double";
                                    }
                                }
                            }
                        }
                        else if (currTypeNode is JArray jArr)
                        {
                            lstType = jArr.Children().Select(t => t.ToString()).ToList();
                        }
                        else
                        {

                        }
                    }

                    if (lstType.Contains("Array") || lstType.Contains("Function"))
                    {
                        csCmt?.AddPara(string.Join("/", lstType));
                    }

                    AddProp(cls, propClassName, propName, defaultValue, csCmt);
                }
            }
            else
            {

            }

            var val = cls.Value;
            
        }

        public static void AddProp(CSClass cls, string propClassName, string propName, string defaultValue, CSComment csCmt)
        {
            if (defaultValue.NotNull() && bool.TryParse(defaultValue.ToString(), out bool blVal))
            {
                if (propClassName.NotEmpty())
                {
                    cls.AddProperty(propClassName, propName, blVal, csCmt);
                }
                else
                {
                    cls.AddProperty(propName, blVal, csCmt);
                }

            }
            else if (defaultValue.NotNull() && int.TryParse(defaultValue.ToString(), out int intVal))
            {
                if (propClassName.NotEmpty())
                {
                    cls.AddProperty(propClassName, propName, intVal, csCmt);
                }
                else
                {
                    cls.AddProperty(propName, intVal, csCmt);
                }
            }
            else if (defaultValue.NotNull() && double.TryParse(defaultValue.ToString(), out double douVal))
            {
                if (propClassName.NotEmpty())
                {
                    cls.AddProperty(propClassName, propName, douVal, csCmt);
                }
                else
                {
                    cls.AddProperty(propName, douVal, csCmt);
                }
            }
            else if (dict_Enum.TryGetValue(propName, out CSEnum csEnum)
                || propName.Equals("animationEasingUpdate", StringComparison.OrdinalIgnoreCase))
            {
                if (propName.Equals("animationEasingUpdate", StringComparison.OrdinalIgnoreCase))
                {
                    csEnum = dict_Enum["animationEasing"];
                }

                cls.AddProperty(csEnum.FullName, propName, defaultValue, csCmt, true);
            }
            else
            {
                if (propClassName.NotEmpty())
                {
                    cls.AddProperty(propClassName, propName, defaultValue, csCmt);
                }
                else
                {
                    cls.AddProperty(propName, defaultValue, csCmt);
                }
            }
        }




    }
}
